using System;
using System.Collections.Generic;
using System.Reflection;
using RikMigrations.Providers;

namespace RikMigrations
{
	public class MigrationManager
	{
		#region [rgn] Fields (3)

		private DbProvider db;
		public const string DefaultModuleName = "DEFAULT";
		private readonly Assembly migrationAssembly;

		#endregion [rgn]

		#region [rgn] Constructors (1)

		public MigrationManager(Assembly assembly, DbProvider provider)
        {
            migrationAssembly = assembly;
            db = provider;
        }
		
		#endregion [rgn]

		#region [rgn] Methods (10)

		// [rgn] Public Methods (5)

		/// <summary>
		/// Gets all migrations in the assembly, keyed by module name and ordered by version
		/// </summary>
		/// <returns></returns>
		public Dictionary<string, List<MigrationAttribute>> FindAllMigrations()
		{
			Dictionary<string, List<MigrationAttribute>> retVal = new Dictionary<string, List<MigrationAttribute>>();
            addMigrationToList(retVal, (MigrationAttribute[])migrationAssembly.GetCustomAttributes(typeof(MigrationAttribute), false), null);

            foreach (Type t in migrationAssembly.GetExportedTypes())
            {
                addMigrationToList(retVal, ( MigrationAttribute[])t.GetCustomAttributes(typeof(MigrationAttribute), false), t);
            }

			return retVal;
		}
		
		public static void MigrateTo(Assembly assembly, string module, int version)
		{
            MigrateTo(assembly, module, version);
		}
		
		public static void MigrateTo(Assembly assembly, string module, int? version, DbProvider db)
		{
            string[] modules = (module == null) ? null : new[] { module };
		    new MigrationManager(assembly, db).MigrateTo(modules, version);
		}
		
		public static void UpgradeMax(Assembly assembly)
		{
		    MigrateTo(assembly, null, null, new MssqlProvider());
		}
		
		public static void UpgradeMax(Assembly assembly, DbProvider db, params string[] moduleNames)
		{
            new MigrationManager(assembly, db).MigrateTo(moduleNames, null);
		}
		
		// [rgn] Private Methods (4)

		private void addMigrationToList(Dictionary<string, List<MigrationAttribute>> retVal, MigrationAttribute[] migrationsFound, Type t)
	    {
            foreach (MigrationAttribute migration in migrationsFound)
            {
                migration.ModuleName = migration.ModuleName ?? DefaultModuleName;

                if (t != null)
                    migration.Type = t;

                if (!retVal.ContainsKey(migration.ModuleName))
                    retVal.Add(migration.ModuleName, new List<MigrationAttribute>());

                retVal[migration.ModuleName].Add(migration);
            }
	    }
		
		private int CompareMigrations(MigrationAttribute x, MigrationAttribute y)
        {
            if (x.Version < y.Version)
                return -1;

            return 0;
        }
				
		private void MigrateTo(string[] modules, int? version)
        {
            Dictionary<string, List<MigrationAttribute>> migration = FindAllMigrations();

            string[] allModules = new string[migration.Count];
            if ((modules == null) || (modules.Length == 0))
            {
                migration.Keys.CopyTo(allModules, 0);
            }
            else
            {
                allModules = modules;
            }

            foreach (string module in allModules)
            {
                if (migration.ContainsKey(module))
                {
                    List<MigrationAttribute> migrations = migration[module];
                    migrations.Sort(CompareMigrations);
                    int currentVersion = db.GetModuleVersion(module);
                    int sourceIndex = migrations.FindIndex(delegate(MigrationAttribute a) { return a.Version == currentVersion; });
                    int targetIndex;
                    
                    if (version != null)
                    {
                        if (version < currentVersion)
                            targetIndex = migrations.FindIndex(delegate(MigrationAttribute a) { return a.Version == version + 1; });
                        else
                            targetIndex = migrations.FindIndex(delegate(MigrationAttribute a) { return a.Version == version; });
                    }
                    else
                    {
                        targetIndex = migrations.Count - 1;
                    }

                    if (sourceIndex < targetIndex)
                    {
                        for (int i = sourceIndex + 1; i <= targetIndex; i++)
                            migrations[i].Up(db);
                    }
                    else if (targetIndex <= sourceIndex)
                    {
                        for (int i = sourceIndex; i >= targetIndex; i--)
                            migrations[i].Down(db);
                    }                    
                }
            }
        }		
		#endregion [rgn]

	}
}